#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/ptrace.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("chzhou");

/*
 * GPBCON ==> 0x56000010
 * GPBDAT ==> 0x56000014
 * GPBUP  ==> 0x56000018
 */
#define GPIO_BASE_ADDR  0x56000000
#define GPBCON_OFFSET   0x10
#define GPBDAT_OFFSET   0x14
#define GPBUP_OFFSET    0x18

struct led_device_t {
    struct cdev dev;
    dev_t no;
    struct class_device *led_classdev;

    unsigned int *io_gpbcon;
    unsigned int *io_gpbdat;
    unsigned int *io_gpbup;
};

struct led_device_t my_led;

void dev_release(struct device *dev)
{
    printk("[%s %d ]\n", __FUNCTION__, __LINE__);
}

struct resource zl_res[] = {
    {
        .start = GPIO_BASE_ADDR,
        .end   = GPIO_BASE_ADDR + SZ_4K,
        .flags = IORESOURCE_IO,
    },
#if 0
    {
        .start = IRQ_EINT2,
        .end   = IRQ_EINT2,
        .flags = IORESOURCE_IRQ,
    },
#endif
};

struct platform_device zl_dev = {
    .name = "zl_device",
    .dev = {
        .release = dev_release,
        .platform_data = &my_led,
    },
    .num_resources = ARRAY_SIZE(zl_res),
    .resource = zl_res,
};

unsigned int devnum = 0;
struct class *my_class;

ssize_t char_read(struct file *fp, char __user *buf, size_t count, loff_t *off)
{
    printk("[%s %d ]\n", __FUNCTION__, __LINE__);
    return 4; //不能返回0, 否则读相关设备时会卡住.
}


ssize_t char_write(struct file *fp, const char __user *buf, size_t count, loff_t *off)
{
    struct led_device_t *get = fp->private_data;
    unsigned int minor;
    char value;

    copy_from_user(&value, buf, 1);

    printk("[%s %d ]0x%x\n", __FUNCTION__, __LINE__, buf[0]);
    printk("[%s %d ] con=0x%08x dat=0x%08x\n", __FUNCTION__, __LINE__,
            *(get->io_gpbcon), *(get->io_gpbdat));

    if ((value >= 0) && (value <= 15)) {
        *(get->io_gpbdat) = ((value & 0xf) << 5);
    } else if((value >= '0') && (value <= '9')){
        *(get->io_gpbdat) = ((value & 0xf) << 5);
    } else if((value >= 'a') && (value <= 'f')){
        *(get->io_gpbdat) = ((value - 97 + 10) << 5);
    } else if((value >= 'A') && (value <= 'F')){
        *(get->io_gpbdat) = ((value - 65 + 10) << 5);
    } else {

    }

    printk("[%s %d ] con=0x%08x dat=0x%08x\n", __FUNCTION__, __LINE__,
            *(get->io_gpbcon), *(get->io_gpbdat));

    return 4;
}

int char_open(struct inode *no, struct file *fp)
{
    fp->private_data = container_of(no->i_cdev, struct led_device_t, dev);
    printk("[%s %d ]\n", __FUNCTION__, __LINE__);

    return 0;
}


int char_release(struct inode *no, struct file *fp)
{
    printk("[%s %d ]\n", __FUNCTION__, __LINE__);
    return 0;
}

struct file_operations my_ops = {
    .open = char_open,
    .release = char_release,
    .read = char_read,
    .write = char_write,
};

int zl_probe(struct device * dev)
{
    int i, ret = 0;
    char *virt = NULL;
    struct platform_device *pdev = container_of(dev, struct platform_device, dev);
    struct resource *res = pdev->resource;
    struct led_device_t *pled = pdev->dev.platform_data;

    for (i = 0; i < pdev->num_resources; i++) {
        if(IORESOURCE_IO & res[i].flags) {
            virt = ioremap(res[i].start, (res[i].end - res[i].start));
            break;
        }
    }

    if (virt == NULL){
        printk("[%s-L%d]:virt == NULL\n", __func__, __LINE__);
        return -1;
    }

    printk("[%s %d ] virt=%p\n", __FUNCTION__, __LINE__, virt);

    pled->io_gpbcon = (unsigned int *)(virt + GPBCON_OFFSET);
    pled->io_gpbdat = (unsigned int *)(virt + GPBDAT_OFFSET);
    pled->io_gpbup  = (unsigned int *)(virt + GPBUP_OFFSET);
    pled->no = MKDEV(52, devnum);

    printk("[%s %d ] con=0x%08x dat=0x%08x\n", __FUNCTION__, __LINE__,
            *(pled->io_gpbcon), *(pled->io_gpbdat));

    cdev_init(&pled->dev, &my_ops);
    cdev_add(&pled->dev, pled->no, 1);

    pled->led_classdev = device_create(my_class, NULL, pled->no, &(pled->dev), "mydev_led.%d", devnum);
    if(IS_ERR(pled->led_classdev)) {
        printk("[%s %d ]\n", __FUNCTION__, __LINE__);
        return PTR_ERR(pled->led_classdev);
    }

    // dump_stack();

    devnum++;
    printk("[%s %d ] devnum=%d\n", __FUNCTION__, __LINE__, devnum);

    return ret;
}

int zl_remove(struct device * dev)
{
    char * virt;
    struct platform_device *pdev = container_of(dev, struct platform_device, dev);
    struct led_device_t *pled = pdev->dev.platform_data;

    virt = (char*)(pled->io_gpbcon);
    virt -= GPBCON_OFFSET;
    iounmap((void*)virt);

    device_destroy(my_class, pled->no);
    cdev_del(&pled->dev);
    printk("driver zl_remove.\n");

    return 0;
}

struct device_driver zl_driver_struct = {
    .name = "zl_device",
    .bus = &platform_bus_type,
    .probe = zl_probe,
    .remove = zl_remove,
};

int mod_init(void)
{
    int ret = 0;

    driver_register(&zl_driver_struct);
    my_class = class_create(THIS_MODULE, "myclass_led");
    if (IS_ERR(my_class)) {
        printk("[%s-%d]: class_create.\n", __func__, __LINE__);
        return PTR_ERR(my_class);
    }

    ret = register_chrdev_region(MKDEV(52,0), 4, "my_led");
    if (ret) {
        printk("[%s %d ] ret=%d\n", __FUNCTION__, __LINE__, ret);
        return ret;
    }

    ret = platform_device_register(&zl_dev);
    if (ret) {
        printk("[%s %d ] ret=%d\n", __FUNCTION__, __LINE__, ret);
        return ret;
    }

    printk("[%s %d ] ret=%d\n", __FUNCTION__, __LINE__, ret);

    return ret;
}

void mod_exit(void)
{
    platform_device_unregister(&zl_dev);
    class_destroy(my_class);
    driver_unregister(&zl_driver_struct);

    printk("[%s %d ]\n", __FUNCTION__, __LINE__);
}

module_init(mod_init);
module_exit(mod_exit);


// http://blogold.chinaunix.net/u4/120285/showart_2564996.html
